package pt.ist.meic.pava;

import java.lang.reflect.Modifier;

import javassist.CtClass;
import javassist.CtMethod;


/**
 * @author Antonio Massingue 
 * @author Joao Matos
 * @author Nuno Santos
 * 
 * 
 * This class contains methods to assist the trait system 
 * implementation  
 *
 */
public class TraitTools {

	
	
	public TraitTools() {	}

	
	
	
	/**
	 * This method verifies if a class is annotated with @Trait
	 * 
	 * @param loadedClass	The class 
	 * @return true if this annotation is present; false otherwise
	 * @throws ClassNotFoundException
	 */
	protected boolean isTrait(CtClass loadedClass)
			throws ClassNotFoundException {

		for (Object traitAnnotation : loadedClass.getAnnotations())
			if (traitAnnotation instanceof Trait)
				return true;

		return false;

	}

	/**
	 * This method verifies if a class is annotated with @WithTraits
	 * 
	 * @param loadedClass	The class
	 * @return	true if this annotation is present; false otherwise
	 * @throws ClassNotFoundException
	 */
	protected boolean isWithTraits(CtClass loadedClass)
			throws ClassNotFoundException {

		for (Object traitAnnotation : loadedClass.getAnnotations())
			if (traitAnnotation instanceof WithTraits)
				return true;

		return false;

	}

	/**
	 * This method verifies whether the loadedClass is 
	 * annotated with @AliasedTraits  
	 *
	 * This annotation is intended to be used exclusively 
	 * within @WithAliasedTraits annotation and therefore
	 * this method may be used to verify erroneous usages 
	 *
	 * 
	 * @param loadedClass
	 * @return	true if this annotation is present; false otherwise
	 * @throws ClassNotFoundException
	 */
	protected boolean isAliasedTraits(CtClass loadedClass)
			throws ClassNotFoundException {

		if(!isWithAliasedTraits(loadedClass))
			for (Object traitAnnotation : loadedClass.getAnnotations())
				if (traitAnnotation instanceof AliasedTrait)
						return true;

		return false;

	}

	/**
	 * This method verifies if a class is annotated with @WithAliasedTraits
	 * 
	 * @param loadedClass	The class to be verified
	 * @return	true if this annotation is present; false otherwise
	 * @throws ClassNotFoundException
	 */
	protected boolean isWithAliasedTraits(CtClass loadedClass)
			throws ClassNotFoundException {

		for (Object traitAnnotation : loadedClass.getAnnotations())
			if (traitAnnotation instanceof WithAliasedTraits)
				return true;

		return false;

	}

	/**
	 * This method verifies if a class is inappropriately used 
	 * within the Trait domain
	 * 
	 * @param loadedClass	The class to be verified
	 * @param superC	The superclass of loadedClass
	 * 
	 * @throws NotTraitException	thrown if loadedClass is not a trait
	 * 
	 * @throws MissingTraitAbstractKeyWordException		thrown if loadedClass is not abstract
	 * 
	 * @throws BadTraitMethodDefinitionException	thrown if the class contains static methods
	 * 
	 * @throws PublicFieldsOnTraitException		thrown if the class contains public fields
	 * 
	 * @throws ClassNotFoundException
	 */
	protected void checkTraitUsage(CtClass loadedClass, CtClass superC)
			throws 
			NotTraitException,
			MissingTraitAbstractKeyWordException,
			BadTraitMethodDefinitionException,
			PublicFieldsOnTraitException,
			ClassNotFoundException {
		
		if (!isTrait(loadedClass))
			throw new NotTraitException(loadedClass);

		/** If the class is not abstract **/
		if (!Modifier.isAbstract(loadedClass.getModifiers()))
			throw new MissingTraitAbstractKeyWordException();

		/** If it contains static methods **/
		for (CtMethod traitMethod : loadedClass.getMethods())
			if (Modifier.isStatic(traitMethod.getModifiers()))
				throw new BadTraitMethodDefinitionException(traitMethod);

		/** If it contains public fields **/
		if (loadedClass.getDeclaredFields().length != 0)
			throw new PublicFieldsOnTraitException(loadedClass);

		/** If the superclass is a trait **/
		if (!superC.getName().equals("java.lang.Object") && !isTrait(superC))
			throw new NotTraitException(loadedClass);

	}

	
	
	
	
	
	/**
	 * This method extracts the traits referred by @WithTraits annotations
	 * of some class
	 * 
	 * @param annotations	The annotations of some class
	 * @return An array containing these traits, if any
	 */
	protected Class<?>[] getTraits(Object[] annotations) {

		Class<?>[] traits = new Class<?>[0];

		for (Object a : annotations)
			if (a instanceof WithTraits) {
				traits = (((WithTraits) a).value());
				break;
			}
		return traits;
	}

	
	
	
	/**
	 * This method extracts the AliasedTraits inside the @WithAliasedTraits
	 * annotations of some class
	 * 
	 * @param annotations	The annotations of some class
	 * @return	An array containing all the AliasedTraits if any
	 */
	protected AliasedTrait[] getAliasedTraits(Object[] annotations) {

		AliasedTrait[] aliasedTraits = new AliasedTrait[0];

		for (Object a : annotations)
			if (a instanceof WithAliasedTraits) {
				aliasedTraits = (((WithAliasedTraits) a).value());
				break;
			}

		return aliasedTraits;

	}

	
	/**
	 * This methods extracts the traits referred by the AliasedTraits
	 * of some class
	 * 
	 * @param aliasedTraits
	 * @return An array containing these traits 
	 */
	protected Class<?>[] extractFromAliased(AliasedTrait[] aliasedTraits) {

		Class<?>[] traits = new Class<?>[aliasedTraits.length];

		for (int i = 0; i < aliasedTraits.length; i++)
			traits[i] = aliasedTraits[i].trait();

		return traits;

	}


	
	/**
	 * This method searches for a specific alias within an array
	 * of aliases
	 * 
	 * @param method	The required alias
	 * @param aliases	The aliases list
	 * @return	The required alias
	 */
	protected String findAlias(String method, String[] aliases) {

		for (int i = 0; i < aliases.length; i += 2)
			if (method.equals(aliases[i]))
				return aliases[++i];

		return method;
	}

	
	/**
	 * This method converts an array of CtMethod objects to
	 * an array of TraitMethod objects
	 * 
	 * @param ctmethods		The array of CtMethods
	 * @param trait		The trait referent to this list of methods
	 * @return an array	of TraitMethod objects referent to this trait 
	 */
	protected TraitMethod[] ctToTraitMethods(CtMethod[] ctmethods,
			LoadedTrait trait) {

		TraitMethod[] tmmethods = new TraitMethod[ctmethods.length];

		int i;
		for (i = 0; i < ctmethods.length; i++)
			tmmethods[i] = ctToTraitMethod(ctmethods[i], trait);

		return tmmethods;

	}

	
	/**
	 * This method converts a CtMethod object to a TraitMethod Object
	 * 
	 * @param ctmethod 		The CtMethod object
	 * @param trait 	The LoadedTrait object referent to this method
	 * @return the method as a TraitMethod object
	 */
	protected TraitMethod ctToTraitMethod(CtMethod ctmethod, LoadedTrait trait) {

		return new TraitMethod(ctmethod, trait.getTrait().getName());

	}


}
